﻿#region License
/**	
The MIT License (MIT)

Copyright (c) 2013 Philipp Schillinger, Michael Alekseew, Andreas Braun, Steeven Zeiß

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. 
 **/
#endregion

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using Emgu.CV;
using Emgu.CV.Structure;

namespace IndoorLocalization.UserInterface {
    /// <summary>
    /// Realizes a window to optimize the placement of the cameras.
    /// </summary>
	public partial class OptimizeWindow : Form {

        /// <summary>
        /// Indicates whether the optimize process is running.
        /// </summary>
		public static bool Running = false;
        /// <summary>
        /// The map on which to optimize.
        /// </summary>
		private Image<Bgr, Byte> map;

        // The factor with which the map image is resized to be displayed.
		private double resizeFactor;
        // Important points used for drawing the selection rectangle.
		private Point startPoint, endPoint, offset = new Point();
        // Indicating whether the region of interest is currently being selected.
		private bool drag = false;

        /// <summary>
        /// The best absolute value of coverage.
        /// </summary>
		private int currentBest = 0;
        /// <summary>
        /// The absolute start value of coverage.
        /// </summary>
		private int startValue = 0;
        /// <summary>
        /// The percentage of how much the optimized result is better than the start value.
        /// </summary>
		private int percentBetter = 0;

        /// <summary>
        /// The region of interest of which the coverage will be optimized.
        /// </summary>
		private Rectangle roi;

        /// <summary>
        /// Creates a new optimize window.
        /// </summary>
        /// <param name="map">The map on which the optimization should be done.</param>
		public OptimizeWindow(Image<Bgr, Byte> map) {
			this.map = map;
			Running = false;

			InitializeComponent();
		}

        /// <summary>
        /// The load event of this window.
        /// </summary>
        /// <param name="sender">Not used.</param>
        /// <param name="e">Not used.</param>
		private void OptimizeWindow_Load(object sender, EventArgs e) {
			startStopButton.Enabled = false;
			imageBox.Image = map;
            generationsValue.Value = Program.OptimizeGenerations;
            childFactorValue.Value = Program.OptimizeChildFactor;
            groupsValue.Value = Program.OptimizeGroups;
		}

        /// <summary>
        /// The close event of this window.
        /// </summary>
        /// <param name="sender">Not used.</param>
        /// <param name="e">Not used.</param>
        private void OptimizeWindow_FormClosing(object sender, FormClosingEventArgs e) {
            Program.OptimizeGenerations = (int)generationsValue.Value;
            Program.OptimizeChildFactor = (int)childFactorValue.Value;
            Program.OptimizeGroups = (int)groupsValue.Value;
            Program.SaveSettings();
        }

        /// <summary>
        /// The click event of the start/stop button.
        /// Starts the optimization if it is currently not running.
        /// Aborts the optimization if it is running.
        /// </summary>
        /// <param name="sender">Not used.</param>
        /// <param name="e">Not used.</param>
		private void startStopButton_Click(object sender, EventArgs e) {
			if (!Running) {
				// Start
				if(roi == null) return;
				Running = true;
				startStopButton.Text = "Stop";
                // Optimize asynchronously
				ThreadPool.QueueUserWorkItem(delegate {
					List<SensorCam> cams = Program.Cameras;
                    FloorPlan.Optimize(ref cams, UpdateProgress, UpdateSuccess, (int)generationsValue.Value, (int)childFactorValue.Value, (int)groupsValue.Value, roi);
                    if (Running) BeginInvoke(new MethodInvoker(delegate{
                        Program.UpdateCoverageMap(null, false);
						startStopButton.Text = "Ok";
					}));
				});

			} else {
				// Stop
				Running = false;
				if (startStopButton.Text == "Ok") {
					progressLabel.Text = "Please select a region to optimize and start the progress.";
					startStopButton.Text = "Optimize!";
				} else {
					progressLabel.Text = "Aborted - improved coverage by " + percentBetter + "%";
					startStopButton.Text = "Optimize!";
                    Program.UpdateCoverageMap(null, false);
				}
			}
		}

        /// <summary>
        /// Updates the progress bar.
        /// Implements the delegate <c>FloorPlan.ProgressCallback</c>.
        /// </summary>
        /// <param name="global">The global progress of the process.</param>
        /// <param name="generation">The progress of a single generation.</param>
		private void UpdateProgress(int global, int generation) {
			BeginInvoke(new MethodInvoker(delegate {
				progressBar.Value = global;
				generationProgressBar.Value = generation;
				if (Running && global == 100)
					progressLabel.Text = "Completed - improved coverage by " + percentBetter + "%";
				else if (Running)
					progressLabel.Text = global + "% done - current improvement: " + percentBetter + "%";

			}));
		}

        /// <summary>
        /// Updates the success indicator.
        /// Implements the delegate <c>FloorPlan.SuccessCallback</c>.
        /// </summary>
        /// <param name="newBest">The currently found new best value.</param>
		private void UpdateSuccess(int newBest) {
			if (newBest < currentBest) return;
			currentBest = newBest;
			if (startValue == 0) {
				startValue = newBest;
			} else {
				percentBetter = (currentBest - startValue) * 100 / startValue;
			}
		}

        /// <summary>
        /// Starts the drag and drop selection of the region of interest.
        /// </summary>
        /// <param name="sender">Not used.</param>
        /// <param name="e">Not used.</param>
		private void imageBox_MouseDown(object sender, MouseEventArgs e) {
			if (imageBox.Image == null || Running) return;
			double resizeHeight = (double)imageBox.Image.Size.Height / imageBox.Height;
			double resizeWidth = (double)imageBox.Image.Size.Width / imageBox.Width;
			if (resizeHeight > resizeWidth) {
				resizeFactor = resizeHeight;
				offset.Y = 0;
				offset.X = -(int)(imageBox.Width - imageBox.Image.Size.Width / resizeFactor) / 2;
			} else {
				resizeFactor = resizeWidth;
				offset.X = 0;
				offset.Y = -(int)(imageBox.Height - imageBox.Image.Size.Height / resizeFactor) / 2;
			}

			endPoint = new Point();
			startPoint = new Point((int)((e.X + offset.X) * resizeFactor), (int)((e.Y + offset.Y) * resizeFactor));
			drag = true;
		}

        /// <summary>
        /// Updates the drag and drop selection of the region of interest.
        /// </summary>
        /// <param name="sender">Not used.</param>
        /// <param name="e">Not used.</param>
        private void imageBox_MouseMove(object sender, MouseEventArgs e) {
			if (drag && !Running) {
				endPoint = new Point((int)((e.X + offset.X) * resizeFactor), (int)((e.Y + offset.Y) * resizeFactor));
				if (startPoint != null) {
					int x = Math.Max(0, Math.Min(startPoint.X, endPoint.X));
					int y = Math.Max(0, Math.Min(startPoint.Y, endPoint.Y));
					roi = new Rectangle(x, y,
										Math.Min(Math.Abs(Math.Max(0,endPoint.X) - Math.Max(0,startPoint.X)), map.Width - x),
										Math.Min(Math.Abs(Math.Max(0,endPoint.Y) - Math.Max(0,startPoint.Y)), map.Height - y));
					Image<Bgr, Byte> toDraw = map.Copy();
					toDraw.Draw(roi, new Bgr(Color.OrangeRed), 2);
					imageBox.Image = toDraw;
				}
			}
		}

        /// <summary>
        /// Stops the drag and drop selection of the region of interest.
        /// </summary>
        /// <param name="sender">Not used.</param>
        /// <param name="e">Not used.</param>
        private void imageBox_MouseUp(object sender, MouseEventArgs e) {
			if (Running) return;
			drag = false;
			startStopButton.Enabled = true;
		}
	}
}
